#include <bsp_lcd.h>
#include <rtthread.h>
#include <gd32f10x.h>

#define DBG_TAG "LCD_BSP"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>

static int lcd_pin_init(void);
static int lcd_spi_init(void);
static int lcd_prepare(void);
static int lcd_tx_dma_init(void);

int lcd_init(void)
{
    lcd_pin_init();
    lcd_spi_init();
#ifdef LCD_USE_DMA
    lcd_tx_dma_init();
#endif

    lcd_prepare();

    LOG_I("lcd init done\n");
    return 0;
}
INIT_APP_EXPORT(lcd_init);

static int lcd_pin_init(void)
{
    /* spi pin */
    rcu_periph_clock_enable(LCD_SPI_GPIO_RCU);
    rcu_periph_clock_enable(RCU_AF);

    gpio_init(LCD_SPI_GPIO_GROUP, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, LCD_SPI_CLK);
    gpio_init(LCD_SPI_GPIO_GROUP, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, LCD_SPI_MISO);
    gpio_init(LCD_SPI_GPIO_GROUP, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, LCD_SPI_MOSI);

    gpio_bit_write(LCD_SPI_GPIO_GROUP, LCD_SPI_CLK, SET);
    gpio_bit_write(LCD_SPI_GPIO_GROUP, LCD_SPI_MISO, SET);
    gpio_bit_write(LCD_SPI_GPIO_GROUP, LCD_SPI_MOSI, SET);

    /* other pin */
    rcu_periph_clock_enable(LCD_CSS_GPIO_RCU);
    rcu_periph_clock_enable(LCD_BLK_GPIO_RCU);
    rcu_periph_clock_enable(LCD_RES_GPIO_RCU);
    rcu_periph_clock_enable(LCD_DC_GPIO_RCU);

    gpio_init(LCD_CSS_GROUP, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, LCD_CSS_PIN);
    gpio_init(LCD_BLK_GROUP, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, LCD_BLK_PIN);
    gpio_init(LCD_RES_GROUP, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, LCD_RES_PIN);
    gpio_init(LCD_DC_GROUP, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, LCD_DC_PIN);

    gpio_bit_write(LCD_CSS_GROUP, LCD_CSS_PIN, SET);
    gpio_bit_write(LCD_BLK_GROUP, LCD_BLK_PIN, SET);
    gpio_bit_write(LCD_RES_GROUP, LCD_RES_PIN, SET);
    gpio_bit_write(LCD_DC_GROUP, LCD_DC_PIN, SET);

    return 0;
}

static int lcd_spi_init(void)
{
    rcu_periph_clock_enable(LCD_SPI_RCU);

    spi_parameter_struct spi_init_struct;
    spi_init_struct.trans_mode           = SPI_TRANSMODE_FULLDUPLEX;
    spi_init_struct.device_mode          = SPI_MASTER;
    spi_init_struct.frame_size           = SPI_FRAMESIZE_8BIT;
    spi_init_struct.clock_polarity_phase = SPI_CK_PL_LOW_PH_1EDGE; // SPI_CK_PL_HIGH_PH_2EDGE SPI_CK_PL_LOW_PH_1EDGE
    spi_init_struct.endian               = SPI_ENDIAN_MSB;
    spi_init_struct.nss                  = SPI_NSS_SOFT;
    spi_init_struct.prescale             = SPI_PSC_4;
    spi_init(LCD_SPI, &spi_init_struct);

    spi_enable(LCD_SPI);

    return 0;
}

#ifdef LCD_USE_DMA
/// @brief
/// @param
/// @return
static int lcd_tx_dma_init(void)
{
    rcu_periph_clock_enable(LCD_DMA_RCU);

    static dma_parameter_struct dma_init_struct;

    dma_init_struct.direction    = DMA_MEMORY_TO_PERIPHERAL;
    dma_init_struct.memory_addr  = 0;
    dma_init_struct.memory_inc   = DMA_MEMORY_INCREASE_ENABLE;
    dma_init_struct.memory_width = DMA_MEMORY_WIDTH_8BIT;
    dma_init_struct.number       = 0;
    dma_init_struct.periph_addr  = (uint32_t)(&(SPI_DATA(LCD_SPI)));
    dma_init_struct.periph_inc   = DMA_PERIPH_INCREASE_DISABLE;
    dma_init_struct.periph_width = DMA_PERIPHERAL_WIDTH_8BIT;
    dma_init_struct.priority     = DMA_PRIORITY_HIGH;

    dma_init(LCD_DMA, LCD_DMA_TX_CHANNEL, &dma_init_struct);

    dma_circulation_disable(LCD_DMA, LCD_DMA_TX_CHANNEL);
    dma_memory_to_memory_disable(LCD_DMA, LCD_DMA_TX_CHANNEL);
    dma_channel_disable(LCD_DMA, LCD_DMA_TX_CHANNEL); /* disable here */

    spi_dma_disable(LCD_SPI, SPI_DMA_TRANSMIT);

    return 0;
}
#endif

/// @brief
/// @param
/// @return
static int lcd_prepare(void)
{
    LCD_RES_Clr(); // 复位
    rt_thread_mdelay(100);
    LCD_RES_Set();
    rt_thread_mdelay(100);

    LCD_BLK_Set(); // 打开背光
    rt_thread_mdelay(100);

    //************* Start Initial Sequence **********//
    lcd_write_reg(0x11);   // Sleep out
    rt_thread_mdelay(120); // Delay 120ms
    //************* Start Initial Sequence **********//
    lcd_write_reg(0x36);
    if (USE_HORIZONTAL == 0)
        lcd_spi_write_byte(0x00);
    else if (USE_HORIZONTAL == 1)
        lcd_spi_write_byte(0xC0);
    else if (USE_HORIZONTAL == 2)
        lcd_spi_write_byte(0x70);
    else
        lcd_spi_write_byte(0xA0);

    lcd_write_reg(0x3A);
    lcd_spi_write_byte(0x05);

    lcd_write_reg(0xB2);
    lcd_spi_write_byte(0x0C);
    lcd_spi_write_byte(0x0C);
    lcd_spi_write_byte(0x00);
    lcd_spi_write_byte(0x33);
    lcd_spi_write_byte(0x33);

    lcd_write_reg(0xB7);
    lcd_spi_write_byte(0x35);

    lcd_write_reg(0xBB);
    lcd_spi_write_byte(0x32); // Vcom=1.35V

    lcd_write_reg(0xC2);
    lcd_spi_write_byte(0x01);

    lcd_write_reg(0xC3);
    lcd_spi_write_byte(0x15); // GVDD=4.8V  颜色深度

    lcd_write_reg(0xC4);
    lcd_spi_write_byte(0x20); // VDV, 0x20:0v

    lcd_write_reg(0xC6);
    lcd_spi_write_byte(0x0F); // 0x0F:60Hz

    lcd_write_reg(0xD0);
    lcd_spi_write_byte(0xA4);
    lcd_spi_write_byte(0xA1);

    lcd_write_reg(0xE0);
    lcd_spi_write_byte(0xD0);
    lcd_spi_write_byte(0x08);
    lcd_spi_write_byte(0x0E);
    lcd_spi_write_byte(0x09);
    lcd_spi_write_byte(0x09);
    lcd_spi_write_byte(0x05);
    lcd_spi_write_byte(0x31);
    lcd_spi_write_byte(0x33);
    lcd_spi_write_byte(0x48);
    lcd_spi_write_byte(0x17);
    lcd_spi_write_byte(0x14);
    lcd_spi_write_byte(0x15);
    lcd_spi_write_byte(0x31);
    lcd_spi_write_byte(0x34);

    lcd_write_reg(0xE1);
    lcd_spi_write_byte(0xD0);
    lcd_spi_write_byte(0x08);
    lcd_spi_write_byte(0x0E);
    lcd_spi_write_byte(0x09);
    lcd_spi_write_byte(0x09);
    lcd_spi_write_byte(0x15);
    lcd_spi_write_byte(0x31);
    lcd_spi_write_byte(0x33);
    lcd_spi_write_byte(0x48);
    lcd_spi_write_byte(0x17);
    lcd_spi_write_byte(0x14);
    lcd_spi_write_byte(0x15);
    lcd_spi_write_byte(0x31);
    lcd_spi_write_byte(0x34);
    lcd_write_reg(0x21);

    lcd_write_reg(0x29);

    return 0;
}

/// @brief spi transmit 1 byte, spi width is 8bit after initialization
/// @param data
void lcd_spi_write_byte(uint8_t data)
{
    LCD_CS_Clr();

    while (spi_i2s_flag_get(LCD_SPI, SPI_FLAG_TBE) == RESET); // wait transmit done

    // if (spi_i2s_flag_get(LCD_SPI, SPI_FLAG_RBNE) != RESET) {
    //     LOG_E("SPI_FLAG_RBNE always set!\n");
    // }

    spi_i2s_data_transmit(LCD_SPI, data);

    /*
       spi_i2s_flag_get only receives data from MISO line,
       truth is this LCD exposed pins lacks MISO line
       so if I use SPI_FLAG_RBNE flag to detect data transmit progress
       may not wait data tranmist done for SPI_FLAG_RBNE always set

       so here use fixed delay to solve the problem
     */

    // while (spi_i2s_flag_get(LCD_SPI, SPI_FLAG_RBNE) == RESET);

    rt_thread_mdelay(2);

    LCD_CS_Set();
}

/// @brief spi transmit 2 byte
/// @param data
void lcd_spi_write_2byte(uint16_t data)
{
    lcd_spi_write_byte(data >> 8);   /* MSB */
    lcd_spi_write_byte(data & 0xff); /* LSB */
}

/// @brief
/// @param dat
void lcd_write_reg(uint8_t dat)
{
    LCD_DC_Clr(); // write command
    lcd_spi_write_byte(dat);
    LCD_DC_Set();
}

void lcd_set_range(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2)
{
    if (USE_HORIZONTAL == 0) {
        lcd_write_reg(0x2a); // 列地址设置
        lcd_spi_write_2byte(x1);
        lcd_spi_write_2byte(x2);
        lcd_write_reg(0x2b); // 行地址设置
        lcd_spi_write_2byte(y1 + 20);
        lcd_spi_write_2byte(y2 + 20);
        lcd_write_reg(0x2c); // 储存器写
    } else if (USE_HORIZONTAL == 1) {
        lcd_write_reg(0x2a); // 列地址设置
        lcd_spi_write_2byte(x1);
        lcd_spi_write_2byte(x2);
        lcd_write_reg(0x2b); // 行地址设置
        lcd_spi_write_2byte(y1 + 20);
        lcd_spi_write_2byte(y2 + 20);
        lcd_write_reg(0x2c); // 储存器写
    } else if (USE_HORIZONTAL == 2) {
        lcd_write_reg(0x2a); // 列地址设置
        lcd_spi_write_2byte(x1 + 20);
        lcd_spi_write_2byte(x2 + 20);
        lcd_write_reg(0x2b); // 行地址设置
        lcd_spi_write_2byte(y1);
        lcd_spi_write_2byte(y2);
        lcd_write_reg(0x2c); // 储存器写
    } else {
        lcd_write_reg(0x2a); // 列地址设置
        lcd_spi_write_2byte(x1 + 20);
        lcd_spi_write_2byte(x2 + 20);
        lcd_write_reg(0x2b); // 行地址设置
        lcd_spi_write_2byte(y1);
        lcd_spi_write_2byte(y2);
        lcd_write_reg(0x2c); // 储存器写
    }
}